<?php
/**
 * 基础的公共函数集合
 * User: devforma
 * Date: 14/12/12
 * Time: 12:00
 */


/**
 * route请求到对应的controller对象
 * @param string $uri 请求的uri
 * @return mixed 该uri找不到对应的controller则返回false，否则返回处理该此请求的controller对象
 */
function getCtrlByUri($uri) {
    $result = parseRequest($uri);
    if (!$result) return false;
    
    extract($result);
    if (!file_exists($filepath)) return false;
    
    include $filepath;
    if (!class_exists($className)) return false;
    
    $ctrl = new $className;
    
    //output方法实现了则返回controller对象，否则返回false
    return method_exists($ctrl, 'output') ? $ctrl : false;
}

/**
 * 解析请求，并对API做路径映射处理
 *
 *  正常返回的信息：array('filepath'=>控制器路径, 'params'=>请求参数, 'className'=>控制器名称)
 *  
 * @param string $uri Request_uri
 * @return array|bool 解析失败时返回FALSE
 */
function parseRequest($uri) {
    global $apiMaps;
    
    if (empty($uri) || $uri=='/') return false;
   
    $parts = parse_url(substr($uri, 1, strlen($uri)-1));
    $basename = basename($parts['path']);
    $pieces = explode('_', $basename);
    if (empty($pieces)) return false;

    $className = '';
    foreach ($pieces as $key => $piece) {
        $className .= ucwords($piece);
    }
    
    if (in_array(strtok($parts['path'], '/'), $apiMaps)) {
        $filepath = sprintf("%s/%s/%s.php", PUBLIC_DIR, dirname($parts['path']), $className);
    } else {
        $filepath = sprintf("%s/apps/%s/%s.php", PUBLIC_DIR, dirname($parts['path']), $className);
    }
    
    $params = array();
    if (isset($parts['query'])) {
        $params = $_GET;
    }
    
    return compact('filepath', 'params', 'className');
}

/**
 * 设置返回数据的数组
 * @param int $respCode 要返回的response_code
 * @param array $data 附加数据
 * @return array 返回数据数组
 */
function genOutput($respCode, $data = array()) {
    global $respCodeMsg;
    $output = array();

    $output['response_code'] = $respCode;

    if ($respCode != 1) {
        $output['response_msg'] = $respCodeMsg["$respCode"];
    }

    if (!empty($data)) {
        $output = array_merge($output, $data);
    }

    return $output;
}

/**
 * 获取用户信息
 * @param int $userId 用户id
 * @return Object|false
 */
function getUserInfo($userId) {
    global $uddb;

    $cacheKey = 'userInfo:'.$userId;

    if (($userInfo = cacheGet($cacheKey)) == null) {

        $sql = "SELECT s.*, a.phone, a.user_email, a.weibo, a.weixin, a.related_phone,s.alipay_id,s.alipay_name FROM `user_social_info` AS s INNER JOIN `user_auth_info` AS a
        ON s.user_id=a.ID WHERE s.user_id={$userId}";
        $userInfo = $uddb->getRow($sql);

        if (empty($userInfo))
            return false;

        //兼容每日一题的头像保存url
        if (!empty($userInfo->avatar) && strpos($userInfo->avatar, 'http://') === false) {
            $userInfo->avatar = 'http://edu.appublisher.com/user-images/avatar/'.$userInfo->avatar;
        }

        cacheAdd($cacheKey, $userInfo, 48*60*60);
    }

    return $userInfo;
}

/**
 * 获取题目的知识点
 *
 * @param array|string $ids 多个ID的字符串以逗号分隔
 * @return array
 */
function getNotesByQuestions($ids) {
    global $cmsdb;

    $data = array();
    if (empty($ids)) return $data;

    if (is_array($ids)) $ids = implode(',', $ids);

    $sql = "SELECT r.*, t.term_name AS name FROM `{$cmsdb->questionNoteR}` r " .
        "LEFT JOIN `data_note_term` t ON(r.note_id=t.id) " .
        "WHERE `question_id` IN($ids)";
    $results = $cmsdb->table()->cache()->raw($sql)->get();
    if (!$results) return $data;

    foreach ($results as $r) {
        $data[$r->question_id]['id'][] = intval($r->note_id);

        if (array_key_exists('name', $data[$r->question_id])) {
            $data[$r->question_id]['name'] .= ',' . $r->name;
        } else {
            $data[$r->question_id]['name'] = $r->name;
        }
    }

    //补齐无关联的题的信息
    $ids = explode(',', $ids);
    foreach ($ids as $qid) {
        if (array_key_exists($qid, $data)) continue;
        $data[$qid] = array('id' => array(0), 'name' => '');
    }

    return $data;
}

/**
 * 根据题目id获取题目数据
 * 
 * @param array $idArray 题目id数组
 * @param array $answerArray 用户答案数组，键为题目id，值为数据对象
 * 
 * @return array
 */
function getQuestions($idArray, $answerArray = array()) {
    global $cmsdb, $uddb;

    $data = array();
    $qidsStr = implode(',', $idArray); //获取题目id查询字符串
    $sql = "SELECT * FROM `{$cmsdb->oZtkQuestions}` WHERE `id` IN ($qidsStr) ORDER BY FIELD(`id`, $qidsStr)";
    $questions = $cmsdb->getResults($sql);
    
    if (!empty($questions)) {
        $notes = getNotesByQuestions($qidsStr);

        //获取分类名与分类id的映射
        $category = array();
        $cat = $uddb->getResults("SELECT * FROM `{$uddb->questionCategory}` WHERE 1");
        foreach ($cat as $c) {
            $category["$c->category_id"] = $c->category_name;
            $category["$c->category_name"] = $c->category_id;
        }

        $questionArray = array();
        foreach ($questions as $q) {
            $questionArray["$q->id"] = $q;
        }

        foreach ($idArray as $id) {
            $tigan = '';
            $q = $questionArray["$id"];
            $options = unserialize($q->options);
            
            if ($q->has_material) {
                $tigan = $q->material;
            }
            
            $tmp = array(
                'id' => intval($q->id),
                'tigan' => getSecureUrl(html_entity_decode($tigan)),
                'content' => getSecureUrl(html_entity_decode($q->question)),
                'option_A' => isset($options[0]) ? getSecureUrl(html_entity_decode($options[0])) : "",
                'option_B' => isset($options[1]) ? getSecureUrl(html_entity_decode($options[1])) : "",
                'option_C' => isset($options[2]) ? getSecureUrl(html_entity_decode($options[2])) : "",
                'option_D' => isset($options[3]) ? getSecureUrl(html_entity_decode($options[3])) : "",
                'answer' => $q->answer,
                'analysis' => getSecureUrl(html_entity_decode($q->analysis)),
                'note_id' => intval($q->note_id),
                'note_ids' => $notes[$q->id]['id'],
                'cat_name' => $q->category,
                'cat_id' => intval($category["$q->category"])
            );

            if (!empty($answerArray)) {
                $tmp['user_answer'] = $answerArray["$q->id"]->answer; //获取用户的答案
                $tmp['is_wrong'] = $answerArray["$q->id"]->is_wrong;
            }
            $data[] = $tmp;
        }
    }
    
    return $data;
}



/**
 * 校验邮箱格式是否正确
 * @param string $str 待检验的字符串
 * @return bool email格式是否正确
 */
function validateEmail($str) {
    return filter_var($str, FILTER_VALIDATE_EMAIL) !== false;
}


/**
 * 校验手机号格式是否正确
 * @param string $str 待检验的字符串
 * @return bool 手机号格式是否正确
 */
function validatePhone($str) {
    return preg_match('/^1[0-9]{10}$/', $str) != 0;
}


/**
 * 校验码格式的校验
 * @param string $str 待检验的字符串
 * @return bool 校验码格式是否正确
 */
function validateCode($str) {
    return preg_match('/^[0-9]{6}$/', $str) != 0;
}

/**
 * uuid格式的校验
 * @param string $str 待检验的字符串
 * @return bool uuid格式是否正确
 */
function validateUUID($str) {
    return preg_match('/^[0-9a-fA-F\-]+$/', $str) != 0;
}

/**
 * 校验用户或游客的有效性
 * 
 * @param array $queryArgs 请求参数
 * @return int 用户无效为0，用户有效且为正式返回1，用户有效且为游客则返回2
 */
function validateUser($queryArgs) {
    global $uddb;

    //先检测正式用户
    if (!empty($queryArgs['user_id']) && is_numeric($queryArgs['user_id']) && !empty($queryArgs['user_token'])) {
        $uInfo = cacheGet('user:' . $queryArgs['user_id']);
        if (!empty($uInfo) && $uInfo['token'] != $queryArgs['user_token']) return 0;
        
        //cache中没有该用户信息，可能内存被回收，需重新设置
        if (empty($uInfo)) {
            $sql = "SELECT `user_token` FROM `{$uddb->userAuthInfo}`
                    WHERE `ID`={$queryArgs['user_id']}";
            $token = $uddb->getVar($sql);
            if ($token != $queryArgs['user_token']) return 0;

            cacheSet('user:' . $queryArgs['user_id'], array('token' => $token), 48*3600);
        }
        
        return 1;

    //检测是否为guest
    } elseif (!empty($queryArgs['guest_id']) && is_numeric($queryArgs['guest_id']) && !empty($queryArgs['user_token'])) {
        $uInfo = cacheGet('guest:' . $queryArgs['guest_id']);
        if (!empty($uInfo) && $uInfo['token'] != $queryArgs['user_token']) return 0;
        
        //cache中没有该用户信息，可能内存被回收，需重新设置
        if (empty($uInfo)) {
            $sql = "SELECT `guest_token` FROM `{$uddb->guestInfo}`
                    WHERE `id`={$queryArgs['guest_id']}";
            $token = $uddb->getVar($sql);
            if ($token != $queryArgs['user_token']) return 0;
            
            cacheSet('guest:' . $queryArgs['guest_id'], array('token' => $token), 48*3600);
        }
            
        return 2;

    //检测是否为游客
    } elseif (empty($queryArgs['user_id']) && empty($queryArgs['user_token'])) {
        return 3;
    }
    
    return 0;
}

/**
 * HTTP网络通信请求
 *
 * @param string $url 请求地址
 * @param string $referer HTTP来源地址
 * @param bool $zip  是否启用压缩传输。默认: false
 *
 * @return string 请求结果
 */
function doCurl($url, $referer = '', $zip = false) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_TIMEOUT,15);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, 'spark/1.0');
    
    if ($referer) {
        curl_setopt($ch, CURLOPT_REFERER, $referer);
    }
    
    if ($zip) {
        $contents = gzinflate(substr(curl_exec($ch),10,-8));
    } else {
        $contents = curl_exec($ch);
    }
    
    $response = curl_getinfo($ch);
    if ($response['http_code']==301 ||$response['http_code']==302){
        curl_setopt($ch, CURLOPT_URL, $response['redirect_url']);
        if ($zip) {
            $contents = gzinflate(substr(curl_exec($ch), 10, -8));
        } else {
            $contents = curl_exec($ch);
        }
    }
    curl_close($ch);
    
    return $contents;
}

/**
 * http post请求
 *
 * @param $url
 * @param $data
 * @return bool|mixed
 */
function httpPost($url, $data) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

    if (strpos($url, 'https://') !== false) {
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    }

    $result = curl_exec($ch);
    curl_close($ch);

    return empty($result) ? false : $result;
}

/**
 * http get请求
 *
 * @param $url
 * @param $data
 * @return bool|mixed
 */
function httpGet($url, $data) {
    $ch = curl_init();
    $url .= '?' . http_build_query($data);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    if (strpos($url, 'https://') !== false) {
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    }

    $result = curl_exec($ch);
    curl_close($ch);

    return empty($result) ? false : $result;
}

/**
 * 获取设置信息
 * 
 * @param string $optName 选项名称
 * @return mixed
 */
function getOption($optName) {
    global $cmsdb;
    
    $cachekey = 'dp_settings';
    $settings = cacheGet($cachekey);
    
    //不存在该项设置
    if (is_array($settings) && !empty($settings["$optName"])) { 
        return $settings["$optName"];
    }
    
    if (!is_array($settings)) $settings = array();
    
    $sql = "SELECT `option_value` FROM {$cmsdb->options} WHERE `option_name`='$optName'";
    $tmpValue = $cmsdb->getVar($sql);
    if (empty($tmpValue)) return '';
    
    $settings["$optName"] = maybe_unserialize($tmpValue);
    
    cacheSet($cachekey, $settings, 60*60); //配置信息的缓存，暂时设为1小时

    return $settings["$optName"];
}

/**
 * 获取可用的考试项目信息
 * 
 * @return array
 */
function getAvailableExams() {
    global $cmsdb;
    
    $cacheKey = 'func:getAvailableExams';
    if ($examList = cacheGet($cacheKey)) return $examList;
    
    $sql = 'SELECT e.exam_name, ed.id, e.shortening, ed.is_official, ed.exam_day FROM `data_exams` AS e INNER JOIN `data_exam_detail` AS ed
        ON e.exam_id=ed.exam_id AND ed.type="笔试" AND UNIX_TIMESTAMP(ed.exam_day)>'.time().' ORDER BY ed.exam_day ASC';
    $exams = $cmsdb->getResults($sql);

    $examList = array();
    foreach ($exams as $exam) {
        if (!isset($examList[$exam->shortening])) { //只取最近的第一个考试
            $examList[$exam->shortening] = array(
                'exam_id' => $exam->id,
                'name' => $exam->exam_name,
                'code' => $exam->shortening,
                'date' => $exam->exam_day,
                'is_official' => $exam->is_official ? true : false,
                'is_interview' => false,
            );
        }
    }

	//国考在最前面
    if (isset($examList['GJ'])) {
        $guokao = $examList['GJ'];
        unset($examList['GJ']);
        array_unshift($examList, $guokao);
    }

    $examList = array_values($examList);
    cacheSet($cacheKey, $examList, 3600*4); //4个小时的缓存
    
    return $examList;
}

/**
 * 版本号比较
 * @param string $version1 版本号1
 * @param string $version2 版本号2
 * @return int 比较结果，1代表version1大于version2，0代表相等，-1代表version2大于version1
 */
function version_cmp($version1, $version2) {
    $vers_1 = explode('.', $version1);
    $vers_2 = explode('.', $version2);

    $len = max(count($vers_1), count($vers_2));

    for($i = 0; $i < $len; $i++) {
        if(is_null($vers_1[$i])) $vers_1[$i] = 0;
        if(is_null($vers_2[$i])) $vers_2[$i] = 0;

        if($vers_1[$i] > $vers_2[$i]) {
            return 1;

        } elseif($vers_1[$i] < $vers_2[$i]){
            return -1;

        }
    }
    return 0;
}

/**
 * 获取加密字符串的原文
 *
 * @param string $data 已加密的字符串
 * @param string $key 加密用的字符串
 *
 * @return string 字符串原文
 */
function passDecrypt($data, $key) {
   $key = md5($key);
   $x = 0;
   $str = '';
   $char = '';
   $data = base64_decode($data);
   $len = strlen($data);
   $l = strlen($key);
   for ($i = 0; $i < $len; $i++)
   {
       if ($x == $l)
       {
           $x = 0;
       }
       $char .= substr($key, $x, 1);
       $x++;
   }
   for ($i = 0; $i < $len; $i++)
   {
       if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1)))
       {
           $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
       }
       else
       {
           $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
       }
   }
   return $str;
}

/**
 * Unserialize value only if it was serialized.
 *
 * @see WordPress wp-includes/functions.php
 *
 * @param string $original Maybe unserialized original, if is needed.
 * @return mixed Unserialized data can be any type.
 */
function maybe_unserialize( $original ) {
	if ( is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
		return @unserialize( $original );
	return $original;
}

/**
 * Check value to find if it was serialized.
 *
 * If $data is not an string, then returned value will always be false.
 * Serialized data is always a string.
 *
 * @see WordPress wp-includes/functions.php
 *
 * @param string $data   Value to check to see if was serialized.
 * @param bool   $strict Optional. Whether to be strict about the end of the string. Default true.
 * @return bool False if not serialized and true if it was.
 */
function is_serialized( $data, $strict = true ) {
	// if it isn't a string, it isn't serialized.
	if ( ! is_string( $data ) ) {
		return false;
	}
	$data = trim( $data );
 	if ( 'N;' == $data ) {
		return true;
	}
	if ( strlen( $data ) < 4 ) {
		return false;
	}
	if ( ':' !== $data[1] ) {
		return false;
	}
	if ( $strict ) {
		$lastc = substr( $data, -1 );
		if ( ';' !== $lastc && '}' !== $lastc ) {
			return false;
		}
	} else {
		$semicolon = strpos( $data, ';' );
		$brace     = strpos( $data, '}' );
		// Either ; or } must exist.
		if ( false === $semicolon && false === $brace )
			return false;
		// But neither must be in the first X characters.
		if ( false !== $semicolon && $semicolon < 3 )
			return false;
		if ( false !== $brace && $brace < 4 )
			return false;
	}
	$token = $data[0];
	switch ( $token ) {
		case 's' :
			if ( $strict ) {
				if ( '"' !== substr( $data, -2, 1 ) ) {
					return false;
				}
			} elseif ( false === strpos( $data, '"' ) ) {
				return false;
			}
			// or else fall through
		case 'a' :
		case 'O' :
			return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
		case 'b' :
		case 'i' :
		case 'd' :
			$end = $strict ? '$' : '';
			return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
	}
	return false;
}

/**
 * Check whether serialized data is of string type.
 *
 * @see WordPress wp-includes/functions.php
 *
 * @param string $data Serialized data.
 * @return bool False if not a serialized string, true if it is.
 */
function is_serialized_string( $data ) {
	// if it isn't a string, it isn't a serialized string.
	if ( ! is_string( $data ) ) {
		return false;
	}
	$data = trim( $data );
	if ( strlen( $data ) < 4 ) {
		return false;
	} elseif ( ':' !== $data[1] ) {
		return false;
	} elseif ( ';' !== substr( $data, -1 ) ) {
		return false;
	} elseif ( $data[0] !== 's' ) {
		return false;
	} elseif ( '"' !== substr( $data, -2, 1 ) ) {
		return false;
	} else {
		return true;
	}
}

/**
 * Serialize data, if needed.
 *
 * @see WordPress wp-includes/functions.php
 *
 * @param string|array|object $data Data that might be serialized.
 * @return mixed A scalar data
 */
function maybe_serialize( $data ) {
	if ( is_array( $data ) || is_object( $data ) )
		return serialize( $data );

	// Double serialization is required for backward compatibility.
	// See https://core.trac.wordpress.org/ticket/12930
	if ( is_serialized( $data, false ) )
		return serialize( $data );

	return $data;
}


/**
 * 根据题目id获取题目内容
 * 
 * @param array $ids 题目id数组
 * @return array
 */
function getQuestionsByIds($ids) {
    global $cmsdb;
    
    if (!is_array($ids) || empty($ids))
        return array();

    $idsStr = implode(',', $ids);
    $notes = getNotesByQuestions($idsStr);

    $sql = 'SELECT q.*, p.name AS paper FROM `original_ztk_questions` AS q INNER JOIN `relation_ztk_paper_question` AS r ON r.question_id=q.id
      INNER JOIN `original_ztk_papers` AS p ON p.id=r.paper_id AND q.id IN ('.$idsStr.') ORDER BY FIELD(q.id, '.$idsStr.')';

    $data = $cmsdb->getResults($sql);

    $list = array();
    $poll = array(); //滤重

    //统计信息列表
    $summaryList = getQuestionQuality($idsStr);

    foreach ($data as $q) {
        $options = unserialize($q->options);

        $qid = intval($q->id);
        if (in_array($qid, $poll, true)) {
            continue;
        }
        $poll[] = $qid;

        $answer = correctAnswer(trim($q->answer));

        //题目统计
        if (isset($summaryList[$q->id])) {
            $summaryAccuracy = $summaryList[$qid]['summary_accuracy'];
            $summaryCount = $summaryList[$qid]['summary_count'];

            $tmpCount = 0;
            $summaryFallible = '';
            foreach ($summaryList[$q->id]['summary_fallible'] as $opt => $count) {
                if ($opt == $answer) continue;

                if ($count > $tmpCount)
                    $summaryFallible = $opt;
            }
        } else {
            $summaryAccuracy = 0.00;
            $summaryCount = 0;
            $summaryFallible = '';
        }


        $list[] = array(
            'id' => $qid,
            'material' => getSecureUrl(html2text(getMaterialById($q->material_id))),
            'question' => getSecureUrl(html2text($q->question)),
            'option_a' => getSecureUrl(html2text(ltrim(ltrim($options[0], 'A'), '.'))),
            'option_b' => getSecureUrl(html2text(ltrim(ltrim($options[1], 'B'), '.'))),
            'option_c' => getSecureUrl(html2text(ltrim(ltrim($options[2], 'C'), '.'))),
            'option_d' => getSecureUrl(html2text(ltrim(ltrim($options[3], 'D'), '.'))),
            'answer' => $answer,
            'analysis' => $q->show_analysis ? getSecureUrl(html2text($q->analysis)) : '',
            'note_id' => intval($notes[$qid]['id'][0]),
            'note_ids' => $notes[$qid]['id'],
            'note_name' => $notes[$qid]['name'],
            'category_id' => getQuestionCategory($q->category),
            'category_name' => $q->category,
            'source' => $q->paper,
            'accuracy' => 0.00,
            'summary_accuracy' => $summaryAccuracy,
            'summary_count' => $summaryCount,
            'summary_fallible' => $summaryFallible,
            'material_id' => (int)$q->material_id,
        );
    }

    return $list;
}

/**
 * 根据题目id获取题目内容  ps:带笔记
 *
 * @param $queryArgs
 * @return array
 */
function getQuestionsAndNotesByIds($queryArgs) {

    $result  = getQuestionsByIds($queryArgs['ids']);
    foreach ($result as $k => $v) {
        //智能练习笔记

        $notes = getQuestionNotes(['user_id' => $queryArgs['user_id'],'question_id' => $v['id']]);

        $result[$k]['question_notes'] = !empty($notes) ? $notes->content : '';
    }
    return $result;
}

/**
 * 去除全半角和大小写的影响
 *
 * @param $answer
 * @return mixed
 */
function correctAnswer($answer) {
    $answer = str_replace(
        array('Ａ', 'Ｂ', 'Ｃ', 'Ｄ', 'ａ', 'ｂ', 'ｃ', 'ｄ'),
        array('A', 'B', 'C', 'D', 'a', 'b', 'c', 'd'),
        $answer);

    return strtoupper($answer);
}

/**
 * 获取知识点层级结构
 * 
 * @return array
 */
function getNoteHierarchy() {
    global $cmsdb;
    
    $cacheKey = 'noteHierarchy';

    if (($value = cacheGet($cacheKey)) != null) {
        return $value;
    } else {
        $value = array(
            '1' => array(),
            '2' => array(),
            '3' => array()
        );

        $sql = 'SELECT * FROM `wp_terms` AS t INNER JOIN `wp_term_taxonomy` AS tt ON t.term_id=tt.term_id AND
        tt.taxonomy="category"';
        $hierarchy = $cmsdb->getResults($sql);

        //构造第一层 --- 科目层
        $subjectIds = array();
        foreach ($hierarchy as $item) {
            if ($item->parent == 0) {
                $value[1][$item->term_id] = array(
                    'name' => $item->name,
                    'hierarchy_name' => $item->name,
                    'id' => $item->term_id,
                    'childs' => array()
                );

                $subjectIds[] = $item->term_id;
            }
        }

        //构造第二层 --- 知识点组层
        $groupIds = array();
        foreach ($hierarchy as $item) {
            if (in_array($item->parent, $subjectIds)) {
                $value[2][$item->term_id] = array(
                    'name' => $item->name,
                    'hierarchy_name' => $value[1][$item->parent]['hierarchy_name'].'-'.$item->name,
                    'id' => $item->term_id,
                    'childs' => array(),
                    'parent' => $item->parent
                );
                $value[1][$item->parent]['childs'][] = $item->term_id;

                $groupIds[] = $item->term_id;
            }
        }

        //构造第三层 --- 知识点层
        foreach ($hierarchy as $item) {
            if (in_array($item->parent, $groupIds) && !in_array($item->name, array('技巧', '考点', '拓展'))) {
                $value[3][$item->term_id] = array(
                    'name' => $item->name,
                    'hierarchy_name' => $value[2][$item->parent]['hierarchy_name'].'-'.$item->name,
                    'id' => $item->term_id,
                    'parent' => $item->parent
                );
                $value[2][$item->parent]['childs'][] = $item->term_id;
            }
        }

        //缓存，24小时的过期时间
        cacheAdd($cacheKey, $value, 24*60*60);

        return $value;
    }
}

/**
 * 根据分类名称或分类id获取id或名称
 * @param int|string $catIdOrName 分类名称或id
 * @return mixed
 */
function getQuestionCategory($catIdOrName) {
    global $cmsdb;
    
    $cacheKey = 'func:getQuestionCategory';

    if (($value = cacheGet($cacheKey)) != null) {
        return $value[$catIdOrName];
    } else {
        $value = array();

        $cats = $cmsdb->getResults('SELECT * FROM `dp_conf_question_category` WHERE 1');
        foreach ($cats as $cat) {
            $value[$cat->category_id] = $cat->category_name;
            $value[$cat->category_name] = intval($cat->category_id);
        }

        cacheAdd($cacheKey, $value, 48*60*60);

        return $value[$catIdOrName];
    }

}

/**
 * html标签的转换
 * @param string $str 待转换字符串
 * @return string
 */
function html2text($str) {
    if (!is_numeric($str) && empty($str))
        return '';

    //网页做题，保留html标签
    if (isset($_GET['original']) && $_GET['original'] == '1') {
        return trim($str);
    }

    $str = str_replace('&nbsp;', ' ', $str);
    $str = html_entity_decode(trim($str));
    $str = strip_tags($str, '<img><p><br>');

    //处理p标签
    $str = preg_replace("/<p[^>]*>/", "\n", $str);
    $str = str_replace(["</p>", "\r\n"], "\n", $str);

    //处理<br>标签
    $str = str_replace(array('<br>', '<br/>', '<br />'), "\n", $str);

    $str = preg_replace("/\n+/", "\n", $str);

    //处理img标签
    $pattern = "/<img.*?src\=[\"'](.*?)[\"'][^>]*>/";
    $str = preg_replace($pattern, "<img=\${1}></img>", $str);

    return trim($str);
}

/**
 * 根据微信id查询用户id
 *
 * @param $openId
 * @param $unionId
 * @return int|null|string
 */
function getUserIdWithWeixin($openId, $unionId) {
    global $uddb;

    $sql = 'SELECT `ID` FROM `user_auth_info` WHERE `weixin`=:weixin LIMIT 1';

    //根据unionid来查
    $exist = $uddb->getVar($sql, array(':weixin' => $unionId));
    if (!empty($exist)) {

        //检测openid是否存在用户
        $exist2 = $uddb->getVar($sql, array(':weixin' => $openId));

        if (!empty($exist2)) {
            //当openid和unionid都存在的时候，需要将上岸计划中openid对应的用户数据改到unionid下，
            //并返回unionid的用户id

            //记录这类用户的信息
            $exist3 = $uddb->getVar('SELECT `id` FROM `weixin_openid_unionid` WHERE `user_id_union`=:uid',
                array(':uid' => $exist));

            //unionid和openid同时存在
            if (empty($exist3)) {

                //修改用户数据
                $uddb->update('dp_user_dailyplan_data', array('user_id' => intval($exist)), array('user_id' => intval($exist2)));
                $uddb->update($uddb->recentMissionInfo, array('user_id' => intval($exist)), array('user_id' => intval($exist2)));
                $uddb->update('dp_history_user_tests', array('user_id' => intval($exist)), array('user_id' => intval($exist2)));
                $uddb->update('dp_history_user_action', array('user_id' => intval($exist)), array('user_id' => intval($exist2)));
                $uddb->update('dp_dailyplan_collection', array('user_id' => intval($exist)), array('user_id' => intval($exist2)));
                $uddb->update('dp_recent_available_mission', array('user_id' => intval($exist)), array('user_id' => intval($exist2)));

                //做记录
                $uddb->insert('weixin_openid_unionid', array(
                    'open_id' => $openId,
                    'user_id_open' => $exist2,
                    'union_id' => $unionId,
                    'user_id_union' => $exist
                ));
            }
        }

        return $exist;
    }

    //根据openid来查
    $exist = $uddb->getVar($sql, array(':weixin' => $openId));
    if (!empty($exist)) {

        //openid更新为unionid
        $uddb->update('user_auth_info', array('weixin' => $unionId), array('ID' => $exist));
        return $exist;
    }

    return 0;
}

/**
 * 获取直播课包含的课堂id数组
 * @param int $courseId 课程id
 * @return mixed
 */
function getCourseClassIds($courseId) {
    global $cmsdb;

    $cacheKey = 'func:getCourseClassIds:'.$courseId;
    if (($classIds = cacheGet($cacheKey)) == null) {

        //获取直播课的课堂id
        $classIds = $cmsdb->getCol('SELECT `class_id` FROM `relationship_live_class_and_course` WHERE `course_id`=:cid
            ORDER BY `course_id` ASC, `class_order` ASC', array(':cid' => $courseId));

        cacheAdd($cacheKey, $classIds, 3600);
    }

    return $classIds;
}

/**
 * 获取课堂详情
 * @param int $classId 课堂id
 * @return mixed
 */
function getLiveClassInfo($classId) {
    global $cmsdb;

    $cacheKey = 'func:getLiveClassInfo:'.$classId;

    if (($class = cacheGet($cacheKey)) == null) {
        $sql = "SELECT c.id, c.class_name, c.start_time, c.end_time, c.lector_id, t.name AS lector_name, t.introduction, t.avatar, t.grade_score, c.room_id,
            c.courseware_link, c.related_paper_id, c.live_platform platform FROM `data_live_class` AS c INNER JOIN `data_teacher` AS t ON c.lector_id=t.id AND c.id=:classid LIMIT 1";
        $class = $cmsdb->getRow($sql, array(':classid' => $classId));

        cacheAdd($cacheKey, $class, 3600);
    }

    return $class;
}


/**
 * 根据指定key进行结构转换,支持数组和简单对象
 *
 * @param array|object $srcData 待转换结构
 * @param array $sortedKeys 转换后结构的键值列表
 * @return array|object
 */
function transformDataStructure(&$srcData, &$sortedKeys) {
    if (empty($sortedKeys))
        return $srcData;

    $isObj = false;
    if (is_object($srcData)) {
        $isObj = true;
        $srcData = (array)$srcData;
    }

    $newData = array();
    $i = 0;
    foreach ($srcData as $value) {
        $newData[$sortedKeys[$i++]] = $value;
    }

    return $isObj ? (object)$newData : $newData;
}

/**
 *	发送邮件
 *
 * @param string $message 邮件内容
 * @param string $subject 邮件标题
 *
 * @return bool
 */
function sendMail($message, $subject = '') {
    $config = include CONFIG_DIR . '/email.config.php';
    if (empty($config)) return false;

    $mail = new PHPMailer();
    $mail->CharSet = "UTF-8";
    $mail->IsSMTP();
    $mail->SMTPDebug  = $config['debug_mode'];
    $mail->SMTPAuth   = true;
    $mail->SMTPSecure = $config['secure_type'];
    $mail->Host       = $config['smtp_host'];
    $mail->Port       = $config['smtp_port'];
    $mail->Username   = $config['smtp_username'];
    $mail->Password   = $config['smtp_password'];
    $mail->SetFrom($config['from'], $config['sender']);
    $mail->Subject    = $subject;
    $mail->AltBody    = "";
    $mail->MsgHTML($message);

    if (!empty($config['to']) && is_array($config['to'])) {
        foreach ($config['to'] as &$addr) {
            $mail->AddAddress($addr, "");
        }
        return $mail->Send();
    }

    return false;
}

/**
 * 获取抢购课程的未支付但有效的订单数
 *
 * @param $courseId
 * @param $currentDatetime
 * @return int
 */
function getValidOrderNum($courseId, $currentDatetime) {
    global $cmsdb;

    //获取未付款,但正在占坑的订单数
    $sql = 'SELECT COUNT(*) FROM `original_orders` WHERE `product_id`=:cid AND `product_type`="zhiboke"
        AND `success`=0 AND `invalid_time`>"'.$currentDatetime.'"';
    $unpurchased = $cmsdb->getVar($sql, array(':cid' => $courseId));

    return intval($unpurchased);
}

/**
 * 获取指定类型的安全链接
 *
 * @param string $subject 原内容
 * @param string $type 内容类型
 * @return string
 */
function getSecureUrl($subject, $type = 'IMG') {
    if ($type == 'IMG') {
        return str_replace('http://dl.cdn.appublisher.com', CDN_IMAGE_URL, $subject);
    }

    return '';
}

/**
 * 获取订单的课程信息
 *
 * @param $courseId
 * @return bool|object
 */
function getOrderCourseInfo($courseId) {
    global $cmsdb;

    $cacheKey = 'order:zhiboke:'.$courseId;

    if (($info = cacheGet($cacheKey)) == null) {
        $sql = 'SELECT `id`, `price`, `class_endtime`, `course_name`, `student_limit`, `time_can_purchase`,
                `end_day`, `is_limited`, `valid_order_period`, `valid_pay_period`, `need_address`, `publish_status`,
                `is_for_scramble` FROM `data_live_course` WHERE `id`=:cid';
        $info = $cmsdb->getRow($sql, array(':cid' => $courseId));

        if ($info) {
            cacheAdd($cacheKey, $info, 20*60); //20分钟缓存
        } else {
            return false;
        }
    }

    return $info;
}

/**
 * 获取订单信息
 *
 * @param $orderId
 * @param $userId
 * @return bool|object
 */
function getOrderInfo($orderId, $userId) {
    global $cmsdb;

    $cacheKey = 'orderInfo:'.$orderId;
    if (($order = cacheGet($cacheKey)) == null || $order->user_id != $userId) {
        $sql = 'SELECT * FROM `original_orders` WHERE `order_id`=:order_id AND `user_id`=:uid AND `success`=0';
        $order = $cmsdb->getRow($sql, array(':order_id' => $orderId, ':uid' => $userId));

        if ($order) {
            cacheAdd($cacheKey, $order, 20*60);
        } else {
            return false;
        }
    }

    return $order;
}

/**
 * 反序列化note_info字段,支持serialize的序列化字符串和json_encode的序列化字符串
 *
 * @param $originalNoteInfo
 * @return array
 */
function normalizeUserNoteInfo($originalNoteInfo) {
    if (isset($originalNoteInfo{0}) && $originalNoteInfo{0} == 'a') {
        return unserialize($originalNoteInfo);
    } else {
        return json_decode($originalNoteInfo, true);
    }
}

/**
 * 获取课程已购人数
 *
 * @param $courseId
 * @return int
 */
function getCoursePurchasedPersons($courseId) {
    global $cmsdb;

    $cacheKey = 'func:getCoursePurchasedPersons:'.$courseId;

    if (($num = cacheGet($cacheKey)) == null) {
        $num = $cmsdb->getVar("SELECT COUNT(*) FROM `relation_user_product` WHERE `product_id`=:cid AND `product_type`='zhiboke'",
            array(':cid' => $courseId));

        cacheAdd($cacheKey, $num, 24*60*60);
    }

    return intval($num);
}

/**
 * 更新购课人数
 *
 * @param $courseId
 * @param int $count
 */
function updateCoursePurchasedPersons($courseId, $count = 0) {
    $cacheKey = 'func:getCoursePurchasedPersons:'.$courseId;

    if ($num = cacheGet($cacheKey)) {
        $num += $count;

        cacheSet($cacheKey, $num, 24*60*60);
    }
}

/**
 *
 *
 * @param int $examDetailId 考试项目子id
 * @return stdClass
 */
function getExamDetail($examDetailId) {
    global $cmsdb;

    $cacheKey = 'func:getExamDetail:'.$examDetailId;

    if (($examDetail = cacheGet($cacheKey)) == null) {
        $sql = "SELECT e.exam_name, e.shortening, ed.exam_day, ed.is_official, ed.type FROM `data_exams` AS e
            INNER JOIN `data_exam_detail` AS ed ON e.exam_id=ed.exam_id AND ed.id={$examDetailId}";
        $testInfo = $cmsdb->getRow($sql);

        if (empty($testInfo))
            return new stdClass();

        $examDetail = array(
            'exam_id' => intval($examDetailId),
            'name' => $testInfo->exam_name,
            'code' => $testInfo->shortening,
            'is_interview' => $testInfo->type == '面试',
            'date' => $testInfo->exam_day,
            'is_official' => $testInfo->is_official == 1
        );

        cacheAdd($cacheKey, $examDetail, 4*60*60);
    }

    return $examDetail;
}


/**
 * 获取考试项目下的知识点信息
 *
 * @param int $testId 考试项目地区的ID
 * @return array 知识点列表
 */
function getNotesByTestId($testId) {
    global $cmsdb;

    return $cmsdb->table($cmsdb->noteQuality)->cache()
        ->where('testarea_id', $testId)
        ->where('available', 1)
        ->where('note_id', '!=', 0)
        ->orderBy('rate', 'DESC')
        ->get();
}


/**
 * 检测用户的接口请求次数是否超限
 *
 * @param array $queryArgs 请求参数
 * @param int $maxAttempts 单位时间最大访问次数,为0表示不加限制
 * @param int $minutes 限制次数的单位时间
 * @return bool
 */
function tooManyAttempts($queryArgs, $maxAttempts = 0, $minutes = 1) {

    //默认无限制
    if ($maxAttempts == 0)
        return false;

    $loginStatus = validateUser($queryArgs);

    if ($loginStatus === 1) { //user_id
        $cacheKey = 'api_limit:user_id:'.$queryArgs['user_id'];
    } elseif ($loginStatus === 2) { //guest_id
        $cacheKey = 'api_limit:guest_id:'.$queryArgs['guest_id'];
    } else {
        return false;
    }

    if ($cacheAttempts = cacheGet($cacheKey)) {
        if ($cacheAttempts > $maxAttempts) {
            return true;
        }

        cacheIncrement($cacheKey, 1);

    } else {
        cacheAdd($cacheKey, 1, $minutes * 60);
    }

    return false;
}

/**
 * 检测用户购买情况
 *
 * @param $userId
 * @param $productId
 * @param $productType
 * @return null|string
 */
function checkUserPurchase($userId, $productId, $productType) {
    global $cmsdb;

    return $cmsdb->getVar('SELECT `id` FROM `relation_user_product` WHERE `user_id`=:uid AND `product_id`=:pid AND `product_type`=:ptype',
        array(':uid' => $userId, ':pid' => $productId, ':ptype' => $productType));
}


/**
 * 获取指定考试项目下的知识点数据
 *
 * @return array
 */
function getNewNoteHierarchy() {
    global $cmsdb;
    $cacheKey = 'func:getNewNoteHierarchy';

    if (($noteData = cacheGet($cacheKey)) == null) {

        $sql = 'SELECT * FROM `data_note_term` WHERE `term_status`=1';
        $notes = $cmsdb->getResults($sql);

        if (empty($notes))
            return array();

        $noteData = array();
        foreach ($notes as $note) {
            $noteData[$note->id] = array(
                'id' => intval($note->id),
                'name' => $note->term_name,
                'parent_id' => intval($note->parent_term_id),
                'note_desc' => $note->note_desc,
                'note_type' => intval($note->term_type),
                'childs' => array()
            );
        }

        cacheAdd($cacheKey, $noteData, 24*60*60);
    }

    return $noteData;
}

/**
 * 根据知识点id查找该知识点下的所有叶子节点
 *
 * @param $searchId
 * @param $availableNotes
 * @return array
 */
function getLeafNotesFromHierarchy($searchId, &$availableNotes) {
    $notes = getNewNoteHierarchy();

    //知识点列表中不存在
    if (!isset($notes[$searchId]))
        return array();

    //该知识点为叶子知识点
    if ($notes[$searchId]['note_type'] == 1)
        return array($searchId);

    //构造知识点树(全知识点覆盖)
    $tree = array();
    foreach ($notes as $noteId => $note) {
        $notes[$noteId]['ids'] = array();

        //把该知识点的直接子知识点作为树的起始
        if ($note['parent_id'] == $searchId) {
            $tree[] = &$notes[$noteId];
        }

        //关联节点父子关系
        $notes[$note['parent_id']]['childs'][] = &$notes[$noteId];
    }

    //递归查找叶子知识点
    $leafIds = array();
    foreach ($tree as $note) {
        $leafIds = array_merge($leafIds, searchLeafNotes($note));
    }

    //取全知识点和用户可见知识点的交集
    return array_intersect($availableNotes, $leafIds);
}

/**
 * 递归查找知识点的叶子节点
 * 
 * @param $note
 * @return mixed
 */
function searchLeafNotes(&$note) {
    if (empty($note['childs'])) {
        return array($note['id']);
    } else {
        foreach ($note['childs'] as &$child) {
            $ids = searchLeafNotes($child);
            $note['ids'] = array_merge($note['ids'], $ids);
        }

        return $note['ids'];
    }
}


/**
 * 获取题目统计
 *
 * @param string $idsStr 题目id字符串
 * @return array
 */
function getQuestionQuality($idsStr) {
    global $cmsdb;

    $sql = "SELECT `question_id`, `test_number`, `correct_number`, `answer_summary` FROM `data_question_quality` WHERE 
    `question_id` IN ({$idsStr}) AND `testarea_id`=0";
    $data = $cmsdb->getResults($sql);

    $list = array();
    foreach ($data as $q) {
        $list[$q->question_id] = array(
            'summary_accuracy' => round($q->correct_number/$q->test_number, 2),
            'summary_count' => intval($q->test_number),
            'summary_fallible' => json_decode($q->answer_summary)
        );
    }

    return $list;
}


/**
 * 有钱idfa推广
 *
 * @param string $appType
 * @param string $idfa
 */
function youqianIdfa($appType, $idfa) {
    global $uddb;

    require CONFIG_DIR.'/appid.config.php';

    switch($appType) {
        case 'dailyplan':
            $appId = DAILYPLAN_APPID;
            break;

        case 'dailylearn':
            $appId = DAILYLEARN_APPID;
            break;

        case 'quizbank':
            $appId = QUIZBANK_APPID;
            break;

        default:
            return;
    }

    //查看是否有友钱上报过来的此条记录，如果有则更新状态，回调友钱
    $sql = 'SELECT * FROM `youqian_idfa_data` WHERE `app_id`=:app_id AND `idfa`=:idfa';
    $row = $uddb->getRow($sql, array(':app_id' => $appId, ':idfa' => $idfa));

    if (empty($row)) {
        $uddb->insert('youqian_idfa_data',array(
            'idfa'          => $idfa,
            'app_id'        => $appId,
            'channel_id'    => 'none',
            'click_time'    => date('Y-m-d H:i:s'),
            'mission_status'=> 1
        ));
    } elseif ($row->channel_id == 'youqian' && $row->mission_status == 0) {
        doCurl($row->callback);
        $uddb->update('youqian_idfa_data', array('mission_status' => 1), array('id' => $row->id));
    }
}

/**
 * 微信广告数据追踪
 *
 * @param $appType
 * @param $idfa
 */
function weixinIdfa($appType, $idfa) {
    global $uddb;

    require CONFIG_DIR.'/appid.config.php';

    switch($appType) {
        case 'dailyplan':
            $appId = DAILYPLAN_APPID;
            $signKey = DAILYPLAN_SIGNKEY;
            $encryptKey = DAILYPLAN_ENCRYPTKEY;
            break;

        case 'dailylearn':
            $appId = DAILYLEARN_APPID;
            $signKey = DAILYLEARN_SIGNKEY;
            $encryptKey = DAILYLEARN_ENCRYPTKEY;
            break;

        case 'quizbank':
            $appId = QUIZBANK_APPID;
            $signKey = QUIZBANK_SIGNKEY;
            $encryptKey = QUIZBANK_ENCRYPTKEY;
            break;

        default:
            return;
    }

    $muid = md5(strtoupper($idfa));

    $exists = $uddb->getRow('SELECT * FROM `youqian_idfa_data` WHERE `app_id`=:appid AND `channel_id`="weixin" AND `idfa`=:muid AND `mission_status`=0',
        [':appid' => $appId, ':muid' => $muid]);

    if ($exists) {
        $timestamp = time();

        $queryString = "click_id={$exists->click_id}&muid={$exists->idfa}&conv_time={$timestamp}";
        $page = "http://t.gdt.qq.com/conv/app/{$exists->app_id}/conv?{$queryString}";

        $encodePage = urlencode($page);
        $signature = md5("{$signKey}&GET&{$encodePage}");

        $baseData = "{$queryString}&sign={$signature}";
        $v = urlencode(base64_encode(simpleXor($baseData, $encryptKey)));

        $attachment = "conv_type=MOBILEAPP_ACTIVITE&app_type=IOS&advertiser_id={$exists->advertiser_id}";

        $finalCallback = "http://t.gdt.qq.com/conv/app/{$appId}/conv?v={$v}&{$attachment}";

        $uddb->update('youqian_idfa_data', ['mission_status' => 1, 'callback' => $finalCallback], ['id' => $exists->id]);

        doCurl($finalCallback);
    }
}

/**
 * @ 熊猫玩回调 - 修改为激活状态 并调用回调url
 * @param $idfa
 * @param $appType
 */
function pandaPlayIdfa($appType , $idfa) {
    global $uddb;

    require CONFIG_DIR.'/appid.config.php';

    switch($appType) {
        case 'dailyplan':
            $appId = DAILYPLAN_APPID;
            break;

        case 'dailylearn':
            $appId = DAILYLEARN_APPID;
            break;

        case 'quizbank':
            $appId = QUIZBANK_APPID;
            break;

        default:
            return;
    }

    $exists = $uddb->getRow('SELECT id,callback FROM `youqian_idfa_data` WHERE `app_id`=:appId  AND  `idfa`=:idfa AND `channel_id`="pandaPlay" AND `mission_status`=0',
        [':idfa' => $idfa ,':appId' => $appId ]);

    if ($exists) {
        $uddb->update('youqian_idfa_data', ['mission_status' => 1], ['id' => $exists->id]);
        doCurl($exists->callback);
    }
}

/**
 * 简单异或加密
 *
 * @param $data
 * @param $key
 * @return string
 */
function simpleXor($data, $key) {
    $retVal = '';

    $j = 0;
    $strArray = str_split($data);
    $keyArray = str_split($key);
    $keyCount = count($keyArray);
    foreach ($strArray as $char) {
        $retVal .= chr(ord($char)^ord($keyArray[$j]));

        $j = $j + 1;
        $j = $j % $keyCount;
    }

    return $retVal;
}

/**
 * 获取考试项目id
 *
 * @param int $examDetailId 考试项目子项目id
 * @return string
 */
function getExamByExamDetail($examDetailId) {
    global $cmsdb;

    $cacheKey = 'func:getExamByExamDetail:'.$examDetailId;

    if (($examId = cacheGet($cacheKey)) == null) {
        $examId = $cmsdb->getVar('SELECT `exam_id` FROM `data_exam_detail` WHERE `id`=:edid', array(':edid' => $examDetailId));

        cacheAdd($cacheKey, $examId, 24*60*60);
    }

    return $examId;
}

/**
 * 检测当前数据实体的etag值与给定值是否匹配
 *
 * @param string $entityId 实体id
 * @return string
 */
function getEtag($entityId) {
    global $cmsdb;

    if (($etag = cacheGet($entityId)) == null) {
        $etag = $cmsdb->getVar('SELECT `etag_value` FROM `etags` WHERE `entity_id`=:entity_id', [':entity_id' => $entityId]);

        if (empty($etag)) {
            //初始化etag
            $etag = md5($entityId . time());
            $cmsdb->insert('etags', ['entity_id' => $entityId, 'etag_value' => $etag]);
        }

        cacheAdd($entityId, $etag);
    }

    return $etag;
}


/**
 * 设置数据实体的etag值
 *
 * @param string $entityId 实体id
 * @param string $etag 给定的etag值
 */
function setEtag($entityId, $etag) {
    global $cmsdb;

    if ($exist = $cmsdb->getVar('SELECT `id` FROM `etags` WHERE `entity_id`=:entity_id', [':entity_id' => $entityId])) {
        $cmsdb->update('etags', ['etag_value' => $etag], ['id' => $exist]);
    } else {
        $cmsdb->insert('etags', ['entity_id' => $entityId, 'etag_value' => $etag]);
    }

    cacheSet($entityId, $etag);
}

/**
 * 统计注册来源渠道
 *
 * @param $queryArgs
 * @return array
 */
function getRegisterInfo($queryArgs) {

    $terminalType = !empty($queryArgs['terminal_type']) ? $queryArgs['terminal_type'] : '';
    $appType      = !empty($queryArgs['app_type']) ? $queryArgs['app_type'] : '';
    $channel      = !empty($queryArgs['channel']) ? $queryArgs['channel'] : '';
    $src          = !empty($queryArgs['src']) ? $queryArgs['src'] : '';
    $school       = !empty($queryArgs['school']) ? $queryArgs['school'] : '';
    $school       = !empty($queryArgs['activity_channel']) ? $queryArgs['activity_channel'] : $school;
    $activity     = !empty($queryArgs['activity']) ? $queryArgs['activity'] : $src;

    $isAndroid    = strpos($terminalType, 'android') !== false;
    $isIOS        = strpos($terminalType, 'iOS') !== false;


    $data = [];

    //注册来源
    if ($isAndroid) { //android
        $data['register_channel'] = 'android';
        $data['register_source'] = $appType;

        //渠道包
        if (!empty($channel)) $data['register_channel'] .= ':'.$channel;

    } elseif ($isIOS) { //iOS
        $data['register_channel'] = 'iOS';
        $data['register_source'] = $appType;

    } else { //web
        $data['register_channel'] = '';
        $data['register_source'] = $terminalType;
    }

    $data['register_activity'] = $activity;
    $data['campus_channel'] = $school;

    return $data;
}
/**
 * 根据 material_id 获取 material信息
 * @param $material_id
 * @return string|null
 */
function getMaterialById($material_id){
    global $cmsdb;

    if (!empty($material_id)) {
        $cacheKey = 'func:getMaterialById:'.$material_id;

        $dataCache = cacheGet($cacheKey);
        if (!empty($dataCache))
            return $dataCache;

        $material = $cmsdb->getVar('SELECT `material` FROM `original_ztk_question_material` WHERE `id`=:material_id',
            array(':material_id' => $material_id));

        if (!empty($material)) {
            cacheAdd($cacheKey, $material, 30*60);
            return $material;
        }
    }
    return null;
}

/**
 * 课程人数扩大
 *
 * @param string $courseStartTime 课程可购买起始时间
 * @param int $numPerMinute 每分钟增加人数
 * @param int $days 持续天数
 * @return float
 */
function getExtraPurchasedNum($courseStartTime, $numPerMinute, $days) {
    $now = time();
    $courseStart = strtotime($courseStartTime);

    if ($now < $courseStart) {
        $extraNum = 0;
    } elseif ($now >= $courseStart && $now < $courseStart + $days*24*60) {
        $extraNum = intval(ceil((time()-strtotime($courseStartTime))/60) * $numPerMinute);
    } else {
        $extraNum = intval($days*24*60*$numPerMinute);
    }

    return $extraNum;
}

/**
 * 获取应用更新记录
 *
 * @param string $appType 应用
 * @param string $platform 系统平台
 * @param string $appVersion 版本号
 * @return object| null
 */
function getAppUpgradeLog($appType, $platform, $appVersion) {
    global $cmsdb;

    //缓存取所有记录
    $cacheKey = 'appupgrade_logs';
    if (($logs = cacheGet($cacheKey)) == null) {
        $logs = (array)$cmsdb->getResults('SELECT * FROM `apps_upgrade_log` WHERE `enable`=1 ORDER BY `id` DESC');

        cacheAdd($cacheKey, $logs, 24 * 60 * 60);
    }

    //计算最新记录和是否需要强制升级
    $forceUpgrade = false;
    $latestVersion = '0.0.0';
    $latestLog = null;
    foreach ($logs as $log) {

        //只在当前app、当前平台、大于当前版本号的记录中进行计算
        if ($log->app_type == $appType && $log->platform == $platform && version_compare($log->app_version, $appVersion) > 0) {

            //只要有一条记录需要强制升级则需要强制升级
            if ($log->force_upgrade)
                $forceUpgrade = true;

            //计算最新的版本记录
            if (version_compare($log->app_version, $latestVersion) > 0) {
                $latestVersion = $log->app_version;
                $latestLog = $log;
            }
        }
    }

    if ($latestLog) {
        $latestLog->force_upgrade = $forceUpgrade;
        return $latestLog;
    }

    return null;
}

/**
 * 更新昨日做题 统计信息
 *
 * @param $examDetailId
 * @param $userNewValue
 * @param $userOldValue
 * @param $userId
 */
function updateYesterdayQuestionCount($examDetailId, $userNewValue, $userOldValue, $userId) {

    $examDetailId   = '';//设置为所有类型
    $thatDay        = date('Ymd');
    $thatCount      = $userNewValue - $userOldValue;
    $cacheKey       = 'func:getYesterdayQuestionCount:' . $examDetailId;
    $cacheKeyRank   = 'func:getYesterdayQuestionCountRank:' . $examDetailId;
    $cacheKeyUser   = 'func:getYesterdayQuestionCountByUser:' . $examDetailId.'_'.$userId;

    //个人当日做题总数
    $todayQuestionCountByUser = updateQuestionCountByUser($cacheKeyUser,$thatDay,$thatCount,$userId);

    //全站当日最高做题数
    updateQuestionCount($cacheKey,$thatDay,$todayQuestionCountByUser);

    //全站当日排名
    $questionCountByUser = getUserDataGlobal($cacheKeyUser);
    $userNewValue = isset($questionCountByUser[$thatDay]) ? $questionCountByUser[$thatDay] : 0;
    $userOldValue = ($userNewValue == 0) ? 0 :$userNewValue - $thatCount;
    updateQuestionCountRank($cacheKeyRank, $thatDay, $userNewValue, $userOldValue);

}

function updateQuestionCountByUser($cacheKeyUser,$thatDay,$thatCount,$userId) {
    //个人当日做题总数
    $questionCountByUser = getUserDataGlobal($cacheKeyUser);

    if (isset($questionCountByUser[$thatDay])) {
        $questionCountByUser[$thatDay] += $thatCount;
    } else {
        $questionCountByUser[$thatDay] = $thatCount;
    }

    $questionCountByUser = clearInvalidDates($questionCountByUser);
    setUserDataGlobal($cacheKeyUser,$questionCountByUser,$userId);

    return $questionCountByUser[$thatDay];
}

function updateQuestionCount($cacheKey,$thatDay,$thatCount) {
    //全站当日最高做题数
    $questionCount = getUserDataGlobal($cacheKey);

    if (isset($questionCount[$thatDay])) {
        if ($questionCount[$thatDay] < $thatCount)
            $questionCount[$thatDay] = $thatCount;
    } else {
        $questionCount[$thatDay] = $thatCount;
    }

    $questionCount = clearInvalidDates($questionCount);
    setUserDataGlobal($cacheKey,$questionCount);
}

function updateQuestionCountRank($cacheKeyRank, $thatDay, $userNewValue, $userOldValue) {
    global $cmsdb;

    //全站当日排名
    $questionCountRank = getUserDataGlobal($cacheKeyRank);
    $rank   = $questionCountRank[$thatDay];

    if (!empty($rank)) {

        if (isset($rank[$userOldValue]) && $rank[$userOldValue] >= 1)
            $rank[$userOldValue]--;

        if (isset($rank[$userNewValue]))
            $rank[$userNewValue]++;
        else
            $rank[$userNewValue] = 1;

    } else {
        $userCount   = $cmsdb->getVar("SELECT count(id) as c FROM `qb_user_data`");
        $rank[0]     = $userCount - 1;
        $rank[$userNewValue] = 1;
    }

    $questionCountRank[$thatDay] = $rank;
    $questionCountRank = clearInvalidDates($questionCountRank);
    setUserDataGlobal($cacheKeyRank,$questionCountRank);
}

/**
 * 清除多余的日期记录
 *
 * @param $arr
 * @return mixed
 */
function clearInvalidDates($arr) {

    $today      = date('Ymd');
    $yesterday  = date('Ymd',strtotime('-1 day'));

    foreach ($arr as $k => $v) {
        if ($k != $today && $k != $yesterday)
            unset($arr[$k]);
    }
    return $arr;
}


/**
 * 更新qb_user_data 排名-估分\总题数\正确率
 *
 * from  quizbank/includes/utils.php getScoreRank 已修改
 * @param $examDetailId
 * @param $fieldName
 * @param $userNewValue
 * @param $userOldValue
 * @param string $userId
 */
function updateUserDataRank($examDetailId, $fieldName, $userNewValue, $userOldValue, $userId='')
{
    $examDetailId = '';//设置为所有类型
    switch ($fieldName) {
        case 'score':
            $cacheKey = 'func:getEvaluateRank:' . $examDetailId;
            break;
        case 'question_count':
            $cacheKey = 'func:getQuestionCountRank:' . $examDetailId;
            break;
        case 'accuracy':
            //正确率时保留两位小数
            $userNewValue = (string)round($userNewValue, 2);
            $userOldValue = (string)round($userOldValue, 2);
            $cacheKey     = 'func:getAccuracyRank:' . $examDetailId;
            break;
        case 'y_question_count':
            //全站当日最高做题数
            updateYesterdayQuestionCount($examDetailId, $userNewValue, $userOldValue, $userId);
            return;
            break;
        default:
            return;
    }

    $rank = cacheGet($cacheKey);
    if (!empty($rank)) {

        if (isset($rank[$userOldValue]) && $rank[$userOldValue] >= 1)
            $rank[$userOldValue]--;

        if (isset($rank[$userNewValue])) {
            $rank[$userNewValue]++;
        } else {
            $rank[$userNewValue] = 1;
        }

    } else {

        $rank[$userNewValue] = 1;

    }
    cacheSet($cacheKey, $rank, 30*24*60*60);
}


/**
 * 获取value值 -同vip下的VipUtils.class.php
 *
 * @param $key
 * @return array|mixed
 */
function getUserDataGlobal($key) {
    global $uddb;

    if (($value = cacheGet($key)) == null) {
        $value = $uddb->getVar("select meta_value from user_data_global where `meta_key`=:meta_key", [':meta_key' => $key]);
        cacheAdd($key, $value, 48*60*60);
    }
    return json_decode($value,true);

}

/**
 * 更新value值 -同vip下的VipUtils.class.php
 *
 * @param $key
 * @param $val
 */
function setUserDataGlobal($key,$val,$userId = 0) {
    global $uddb;

    $exist = $uddb->getVar("select id from user_data_global where `meta_key`=:meta_key",[':meta_key' => $key]);

    if (!empty($exist)) {
        $data = [
            'meta_value'    => json_encode($val)
        ];
        $uddb->update('user_data_global', $data, ['id' => $exist]);

    } else {
        $data = [
            'user_id'       => $userId,
            'meta_key'      => $key,
            'meta_value'    => json_encode($val),
            'updated_time'  => date('Y-m-d H:i:s'),
            'created_time'  => date('Y-m-d H:i:s')
        ];
        $uddb->insert('user_data_global', $data);
    }
    cacheSet($key, json_encode($val), 48*60*60);
}

/**
 * 获取行测笔记
 *
 * @param $queryArgs
 * @return array|null
 */
function getQuestionNotes($queryArgs) {
    global $uddb;

    $result = $uddb->getRow("select * from user_question_notes where user_id = :uId   and question_id = :qId",
        [
            ':uId'  => $queryArgs['user_id'],
            ':qId'  => $queryArgs['question_id']
        ]);
    return $result;
}